import * as THREE from "three"
import * as NP from "number-precision"

export default class Ball {
  settings: IBallCollision.BallSettings
  size: number = 1
  color: THREE.Color = new THREE.Color()
  opacity: number = 1
  direction: number = Math.PI / 2
  position: THREE.Vector3 = new THREE.Vector3()
  object: THREE.Group = new THREE.Group()
  directionArrow: THREE.ArrowHelper | undefined = undefined
  constructor(settings: IBallCollision.BallSettings) {
    this.settings = {...settings}
    this.color = this.getColor()
    this.opacity = this.getOpacity()
    this.position = this.getPosition()
    this.direction = this.getDirection()
    this.size = this.getSize()
    this.object = this.buildObject()
    if (this.settings.showDirectionArrow) {
      this.directionArrow = this.buildDirectionArrow()
    }
  }

  buildObject(): THREE.Group {
    const group = new THREE.Group()
    group.position.copy(this.position)
    const object = new THREE.Mesh(
      new THREE.CircleGeometry(this.size, 32),
      new THREE.MeshBasicMaterial({
        color: this.color,
        side: THREE.DoubleSide,
        transparent: true,
        opacity: this.opacity,
      })
    )
    group.add(object)
    return group
  }

  buildDirectionArrow() {
    const arrow = new THREE.ArrowHelper(this.getArrowDirection(this.direction), new THREE.Vector3(0, 0, 0), 3, this.color, .5, .5)
    this.object.add(arrow)
    return arrow
  }

  getArrowDirection(direction: number) {
    const vec = this.transferVectorByAngle(new THREE.Vector2(1, 0), direction)
    return new THREE.Vector3(vec.x, vec.y, 0)
  }

  transferVectorByAngle(vec: THREE.Vector2, angle: number): THREE.Vector2 {
    const vec2 = new THREE.Vector2()
    vec2.x = NP.minus(NP.times(vec.x, Math.cos(angle)), NP.times(vec.y, Math.sin(angle)))
    vec2.y = NP.plus(NP.times(vec.x, Math.sin(angle)), NP.times(vec.y, Math.cos(angle)))
    return vec2.normalize()
  }

  getSize() {
    const sizeRange = this.settings.sizeRange || [.8, 1.2]
    return sizeRange[0] + Math.random() * (sizeRange[1] - sizeRange[0])
  }

  getColor(): THREE.Color {
    const colorSet = this.settings.colorSet || []
    const randomIdx = Math.floor(colorSet.length * Math.random())
    return new THREE.Color(colorSet[randomIdx])
  }
  getOpacity(): number {
    const opacityRange = this.settings.opacityRange || [.5, 1]
    return opacityRange[0] + Math.random() * (opacityRange[1] - opacityRange[0])
  }

  getPosition(): THREE.Vector3 {
    console.error("this.settings.cameraRect", this.settings.cameraRect)
    return new THREE.Vector3(
      Math.random() * (this.settings.cameraRect.x - this.size) * (Math.random() > .5 ? 1 : -1),
      Math.random() * (this.settings.cameraRect.y - this.size) * (Math.random() > .5 ? 1 : -1),
      0
    )
  }

  getDirection(): number {
    const randomDirection = Math.random() * Math.PI * 2
    // 夹角小于10'或者大于80'时候放弃，因为太垂直或水平运动路线有可能一直贴着边没有撞击反弹效果
    if (
      Math.abs(Math.sin(randomDirection)) < Math.sin(THREE.MathUtils.degToRad(10)) 
      || Math.abs(Math.sin(randomDirection)) > Math.sin(THREE.MathUtils.degToRad(80))
    ) {
      return this.getDirection()
    }
    return randomDirection
  }

  updateArrowDirection() {
    if (this.directionArrow) {
      this.directionArrow.setDirection(this.getArrowDirection(this.direction))
    }
  }

  run() {
    const speed = this.settings.speed || .1
    const cameraRect = this.settings.cameraRect
    const maxX = cameraRect.x - this.size
    const maxY = cameraRect.y - this.size
    const speedX = speed * Math.cos(this.direction)
    const speedY = speed * Math.sin(this.direction)
    this.object.position.x += speedX
    this.object.position.y += speedY
    if (Math.abs(this.object.position.x) > maxX) {
      this.direction = Math.PI - this.direction
      this.updateArrowDirection()
      return
    }
    if (Math.abs(this.object.position.y) > maxY) {
      this.direction = Math.PI * 2 - this.direction
      this.updateArrowDirection()
    }
  }
}